package com.rongji.dfish.misc.util.json.impl;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.NumericNode;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.google.gson.*;
import com.rongji.dfish.base.util.LogUtil;
import com.rongji.dfish.base.util.Utils;
import com.rongji.dfish.base.util.json.impl.AbstractJsonBuilder;

import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author lamontYu
 * @since
 */
public class JsonBuilder4Jackson extends AbstractJsonBuilder {

    private ObjectMapper objectMapper;

    protected ObjectMapper getObjectMapper() {
        if (objectMapper == null) {
            objectMapper = new ObjectMapper();
            if (Utils.notEmpty(dateFormat)) {
                objectMapper.setDateFormat(new SimpleDateFormat(dateFormat));
            }
            objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);

        }
        return objectMapper;
    }

    @Override
    public String toJson(Object object) throws Exception {
        return getObjectMapper().writeValueAsString(object);
    }

    @Override
    public <T> T parseObject(String json, Class<T> objectClass) throws Exception {
        return getObjectMapper().readValue(json, objectClass);
    }

    @Override
    public <T> List<T> parseArray(String json, Class<T> objectClass) throws Exception {
        ObjectMapper mapper = getObjectMapper();
        CollectionType collectionType = mapper.getTypeFactory().constructCollectionType(List.class, objectClass);
        List<T> list = mapper.readValue(json, collectionType);
        return list;
    }

    @Override
    public Map<String, String> parseAsStringPairs(String json) throws Exception {
        ObjectMapper mapper = getObjectMapper();
        JsonNode node = mapper.readTree(json);
        LinkedHashMap<String, String> ret = new LinkedHashMap<>(node.size());
        visit4StringPairs(node, null, ret);
        return ret;
    }

    private void visit4StringPairs(JsonNode node, String prefix, LinkedHashMap<String, String> ret) {
        if (JsonNodeType.OBJECT.equals(node.getNodeType())) {
            Iterator<String> names = node.fieldNames();
            while (names.hasNext()) {
                String key = names.next();
                String currentKey = prefix == null ? key : (prefix + "." + key);
                visit4StringPairs(node.get(key), currentKey, ret);
            }
        } else if (JsonNodeType.ARRAY.equals(node.getNodeType())) {
            for (int i = 0; i < node.size(); i++) {
                String currentKey = prefix == null ? String.valueOf(i) : (prefix + "." + i);
                visit4StringPairs(node.get(i), currentKey, ret);
            }
        } else {
            if (node != null) {
                ret.put(prefix, node.asText());
            }
        }
    }

    @Override
    public Object parseObject(String json) throws Exception {
        ObjectMapper mapper = getObjectMapper();
        JsonNode node = mapper.readTree(json);
        return visit(node);
    }

    private Object visit(JsonNode node) {
        switch (node.getNodeType()) {
            case OBJECT:
                Iterator<String> names = node.fieldNames();
                Map<String, Object> map = new LinkedHashMap<>();
                while (names.hasNext()) {
                    String key = names.next();
                    map.put(key, visit(node.get(key)));
                }
                return map;
            case ARRAY:
                List<Object> arr = new ArrayList<>(node.size());
                ;
                for (int i = 0; i < node.size(); i++) {
                    arr.add(visit(node.get(i)));
                }
                return arr;
            case NULL:
                return null;
            case NUMBER:
                NumericNode num = (NumericNode) node;
                switch (num.numberType()) {
                    case INT:
                        return num.asInt();
                    case LONG:
                        return num.asLong();
                    case FLOAT:
                    case DOUBLE:
                        return num.asDouble();
                    case BIG_DECIMAL:
                        return num.decimalValue();
                    case BIG_INTEGER:
                        return num.bigIntegerValue();
                }
                return null;
            case BOOLEAN:
                return node.asBoolean();
            case STRING:
                return node.asText();
            default:
                LogUtil.info("不可识别的类型:" + node.getClass().getName());
                return null;
        }
    }

}
